home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-04-18 | 15.8 KB | 418 lines | [TEXT/MMCC] |
- /*
- NoisePdfFill.c
-
- error=NoisePdfFill(world,&rect,pdfKind,&mean,&sd,min,max);
- error=NoisePdfAdd(world,&rect,pdfKind,&mean,&sd,min,max);
-
- Generate good noise images quickly. You supply a GWorld (or a window) and a rect
- indicating how much of it to fill with noise. "world" may be a pointer to a
- GWorld, a color CWindow, or, with casting, a plain-old Window. NoisePdfFill fills
- the pixels within rect with noise; NoisePdfAdd adds noise to them. Each pixel
- will get an independent integer sample from a specified probability density
- function (pdf): kBinaryPdf, kUniformPdf, or kGaussianPdf. (A fourth pdf,
- kBinomialPdf, is not recommended.) You specify the pdf kind, mean, and
- "unclipped" standard deviation. The last two arguments, "min" and "max", are used
- to clip, i.e. restrict the range of the pdf (e.g. to preclude overflow in your
- image pixels). In the cases of kUniformPdf, kBinomialPdf, and kGaussianPdf,
- samples falling outside the range [min,max] are discarded and replaced by
- independent new samples. In the case of kBinary, each sample has one of only two
- values, with equal probability; these two values are initially set to mean±sd, to
- yield the desired mean and sd, but they are adjusted, if necessary, to bring them
- within the range [min,max]. Upon return, the mean and sd arguments (which are
- passed by reference, i.e. by address, not by value) return the mean and standard
- deviation of the random generator of the samples (i.e. the clipped pdf).
-
- The requested standard deviation, sd, must be greater than or equal to zero. The
- special case of zero standard deviation is recognized and performed quickly,
- producing a uniform image with specified mean. A request for an unachievably low,
- but nonzero, sd will be served by providing the lowest achievable sd (given the
- specified min and max), on the premis that the user cares most about log sd, so
- that the least possible nonzero sd will be a better approximation to an
- unachievably low sd than would zero sd. Supplying an sd of INF will produce
- samples with values of min and max if the pdf kind is kBinaryPdf, and will
- produce a uniform distribution over the range [min,max] if the pdf is
- kUniformPdf, kGaussianPdf, or kBinomialPdf.
-
- "max" must be greater than or equal to "mean", which must be greater than or
- equal to "min". Normally, image pixels are considered positive and you'll want to
- make "min" greater than or equal to zero, but this is not enforced by
- NoisePdfFill; e.g. you can add zero-mean noise to a positive image.
-
- Calling NoisePdfAdd with a "min"<0 should work perfectly (though it hasn't been
- tested), but may be significantly slower than when "min"≥0. NoisePdfAdd() uses
- NoisePdfFill() to make the noise in a temporary new GWorld and then calls
- CopyWindows to add the noise to your image. If "min"≥0 then NoisePdfAdd() uses a
- fast parallel addition that assumes there will be no overflow in each pixel's
- arithmetic, so the user should take care to specify a "max" that will indeed
- prevent overflow, since any overflow would affect a neighboring pixel. If "min"<0
- then the unsigned addition of "negative" with positive pixels to produce positive
- pixels will "overflow" in the normal course of computing the sum using unsigned
- arithmetic. The bits of the pixel itself will be correct, but the overflow would
- affect a neighboring pixel if parallel addition were used, so the additions are
- done independently, one pixel at a time, which takes longer.
-
- kBinaryPdf, kUniformPdf, and kGaussianPdf should all be useful. We use
- kGaussianPdf in most of our noise-masking work, but for dynamic noise we
- sometimes use kBinaryPdf in order to achieve more noise power, assuming that the
- observer will visually integrate several of the dynamic frames, blurring the
- distinction between pdf's. Presumably the three kinds of noise would be visually
- indistinguishable if shown with independent 15 ms frames and equal sd.
-
- kBinomialPdf works fine, but is not recommended. The kBinomialPdf code uses the
- fact that the number of heads in 24 coin tosses is approximately gaussian. This
- code was originally written as a quick way to make approximately gaussian noise.
- However, kBinomialPdf is probably much slower than the subsequently written
- kGaussianPdf, which provides samples that are accurately gaussian.
-
- void GetGoodNoisePdfBounds(pdfKind,mean,clippedSd,&unclippedSd,&min,&max);
-
- GetGoodNoisePdfBounds is a utility routine that helps you choose good parameters
- with which to call NoisePdfFill or NoisePdfAdd. Be aware that, whereas
- NoisePdfFill and NoisePdfAdd are general-purpose, the "good" in the name
- "GetGoodNoisePdfBounds" indicates that its recommendations are not necessarily
- best. It incorporates several reasonable but somewhat arbitrary assumptions, e.g.
- that you'd like to clip your gaussian at ±2 sd, and helps you to choose a good
- set of parameters to obtain that result. GetGoodNoisePdfBounds will not be
- appropriate to all uses of NoisePdfFill and NoisePdfAdd; you may want to write
- your own. Note: GetGoodNoisePdfBounds is still under development; I haven't yet
- added its prototype to VideoToolbox.h.
-
- The hardest part of writing the code in this file was figuring out how to
- separate the general-purpose code (i.e. NoisePdfFill) from the pragmatic but
- somewhat arbitrary assumptions now encapsulated in GetGoodNoisePdfBounds.
-
- HISTORY:
- 3/20/95 dgp wrote it, based on my MakeTextInNoiseWorld.c
- 4/8/95 dgp removed any assumption that world is a GWorldPtr; it can also
- be a CWindowPtr or a WindowPtr.
- 4/18/95 dgp fixed error in NoisePdfAdd: mean & sd were swapped.
- */
-
- #include "VideoToolbox.h"
- #include <assert.h>
- #include <math.h>
- OSErr NoisePdfFill(GWorldPtr world,Rect *rectPtr
- ,int pdfKind,double *meanPtr,double *sdPtr,int min,int max);
- OSErr NoisePdfAdd(GWorldPtr world,Rect *rectPtr
- ,int pdfKind,double *meanPtr,double *sdPtr,int min,int max);
- void GetGoodNoisePdfBounds(int pdfKind,double mean,double clippedSd
- ,double *unclippedSdPtr,int *minPtr,int *maxPtr);
-
- //enum{kBinaryPdf=0,kBinomialPdf,kGaussianPdf,kUniformPdf}; // in VideoToolbox.h
-
- #undef MAX
- #undef MIN
- #define MAX(a,b) ((a) > (b) ? (a) : (b))
- #define MIN(a,b) ((a) < (b) ? (a) : (b))
- static Boolean diagnostics=0;
-
- OSErr NoisePdfFill(GWorldPtr world,Rect *rectPtr
- ,int pdfKind,double *meanPtr,double *sdPtr,int min,int max)
- {
- CGrafPtr oldPort;
- GDHandle oldDevice;
- int error=0;
- OSErr osErr=0;
- long n;
- int i,j,x,y;
- long low,high,sample;
- double actualMean,actualSd; // mean and sd of process that generated the image
- long actualMin,actualMax;
- int binomialSamples;
- double binomialFactor;
- long extraSamples;
- short *distribution;
- static short **distributionHandle=NULL;
- static long distributionSize;
- static double lowSave,highSave,meanSave,unclippedSdSave,actualSdSave;
- double meanSquare,a,aa;
- RGBColor rgb;
- GDHandle device;
- int pixelSize;
- static const RGBColor blackRGB={0,0,0},whiteRGB={0xFFFF,0xFFFF,0xFFFF};
- unsigned long pix[2048];
- register long *pL;
-
- // Make noise. Takes 300 ms
- assert(StackSpace()>4000);
- assert(world!=NULL && sdPtr!=NULL && meanPtr!=NULL);
- assert(!IsNan(*meanPtr) && !IsNan(*sdPtr));
- assert(*sdPtr>=0.0);
- assert(min<=*meanPtr && *meanPtr<=max);
- high=max;
- low=min;
- if(IsInf(*sdPtr) && pdfKind!=kBinaryPdf)pdfKind=kUniformPdf;
- switch(pdfKind){
- case kBinaryPdf:
- // high and low are values corresponding to heads and tails.
- // The high-low difference is more important than their individual values.
- if(!IsInf(*sdPtr)){
- a=2*(*sdPtr);
- low=MAX(floor(0.5+*meanPtr-a/2),min);
- high=MIN(floor(0.5+low+a),max);
- low=MAX(floor(0.5+high-a),min);
- if(low==high && *sdPtr>0){
- if(high<max)high++;
- else if(low>min)low--;
- }
- }
- break;
- case kUniformPdf:
- if(!IsInf(*sdPtr)){
- a=2*sqrt(3)*(*sdPtr)-1; // integer range of pdf for specified sd
- low=MAX(floor(0.5+*meanPtr-a/2),min);
- high=MIN(floor(0.5+low+a),max);
- low=MAX(floor(0.5+high-a),min);
- if(low==high && *sdPtr>0){
- if(high<max)high++;
- else if(low>min)low--;
- }
- }
- break;
- default:
- break;
- }
- GetGWorld(&oldPort,&oldDevice);
- device=GetWindowDevice((WindowPtr)world);
- pixelSize=(**(**device).gdPMap).pixelSize;
- if(low==high){
- SetGWorld(world,device);
- Index2Color(low,&rgb);
- RGBBackColor(&rgb);
- EraseRect(rectPtr);
- SetGWorld(oldPort,oldDevice);
- actualMean=low;
- actualSd=0;
- goto done;
- }
- switch(pdfKind){
- case kBinaryPdf:
- SetGWorld(world,device);
- Index2Color(low,&rgb);
- RGBBackColor(&rgb);
- Index2Color(high,&rgb);
- RGBForeColor(&rgb);
- error=NoiseFill(world,rectPtr,1,1,0);
- RGBBackColor(&whiteRGB);
- RGBForeColor(&blackRGB);
- SetGWorld(oldPort,oldDevice);
- actualSd=0.5*(high-low);
- actualMean=0.5*(high+low);
- break;
- case kUniformPdf:
- n=1+high-low;
- assert(rectPtr->right-rectPtr->left<=sizeof(pix)/sizeof(*pix));
- for(y=rectPtr->top;y<rectPtr->bottom;y++){
- pL=(long *)pix;
- for(x=rectPtr->right-rectPtr->left-1;x>=0;x--)
- *pL++=low+nrand(n);
- SetWindowPixelsQuickly((WindowPtr)world,rectPtr->left,y
- ,pix,rectPtr->right-rectPtr->left);
- }
- assert(n<=sizeof(pix)/sizeof(*pL));
- pL=(long *)pix;
- for(x=low;x<=high;x++)*pL++=x;
- actualMean=MeanL((long *)pix,1+high-low,&actualSd);
- if(diagnostics)printf("unclippedSd %f, actualSd %f, actualSd/unclippedSd %f\n"
- ,*sdPtr,actualSd,actualSd/(*sdPtr));
- break;
- case kGaussianPdf:
- /*
- First we make an ordered list of about twenty thousand integers, with
- frequency proportional to the desired probability. This table only needs
- to be made once and can be reused indefinitely, until the parameters
- (mean, sd, min, max) of the distribution change. Each noise pixel is
- a random sample from the table.
- */
- if(distributionHandle==NULL || low!=lowSave || high!=highSave
- || IsInf(*sdPtr)!=IsInf(unclippedSdSave)
- || (!IsInf(*sdPtr) && fabs(*sdPtr/unclippedSdSave-1)>.01)
- || *meanPtr!=meanSave){
- distributionSize=256*100L;
- if(distributionHandle==NULL){
- distributionHandle=(short **)TempNewHandle(distributionSize*sizeof(*distribution),&osErr);
- error=osErr;
- if(distributionHandle==NULL){
- distributionHandle=(short **)NewHandle(distributionSize*sizeof(*distribution));
- error=MemError();
- }else error=0;
- if(distributionHandle==NULL)PrintfExit("Sorry, couldn't allocate %ld bytes "
- "for a gaussian table. File “%s” line %d.\n"
- ,distributionSize*sizeof(*distribution),__FILE__,__LINE__);
- }
- if(fabs((high-*meanPtr)-(highSave-meanSave))>0.5 || high-low!=highSave-lowSave
- || IsInf(*sdPtr)!=IsInf(unclippedSdSave)
- || (!IsInf(*sdPtr) && fabs(*sdPtr/unclippedSdSave-1)>.01)){
- // takes 300 ms
- BoundedNormalIntegers(*distributionHandle,distributionSize
- ,*meanPtr,*sdPtr,low,high);
- }else{
- j=floor(0.5+*meanPtr-meanSave);
- distribution=*distributionHandle;
- if(j!=0)for(i=0;i<distributionSize;i++)distribution[i]+=j;
- *meanPtr=meanSave+j;
- }
- lowSave=low;
- highSave=high;
- unclippedSdSave=*sdPtr;
- meanSave=*meanPtr;
- actualMean=MeanW(*distributionHandle,distributionSize,&actualSdSave);
- }
- actualSd=actualSdSave;
- assert(rectPtr->right-rectPtr->left<=sizeof(pix)/sizeof(*pix));
- //HLock((Handle)distributionHandle);
- distribution=*distributionHandle;
- for(y=rectPtr->top;y<rectPtr->bottom;y++){
- pL=(long *)pix;
- for(x=rectPtr->right-rectPtr->left-1;x>=0;x--)
- *pL++=distribution[nrand(distributionSize)];
- SetWindowPixelsQuickly((WindowPtr)world,rectPtr->left,y
- ,pix,rectPtr->right-rectPtr->left);
- }
- if(diagnostics)printf("unclippedSd %f, actualSd %f, actualSd/unclippedSd %f\n"
- ,*sdPtr,actualSd,actualSd/(*sdPtr));
- break;
- case kBinomialPdf:
- // Use the number of heads in 24 coin flips as a random variable.
- // It can be computed quickly and is approximately normal.
- // Shift and scale to obtain the specified mean and variance.
- // Discard samples outside the range [min,max].
- // Return mean and sd of the clipped distribution.
- binomialSamples=24; // should be even (for integer divide by 2)
- binomialFactor=*sdPtr/sqrt(binomialSamples*0.25);
- /*
- The empirical approach of actually measuring the mean and variance
- of our noise generator demands that we produce a
- reasonable number of samples, at least 1000. Any extra samples are later
- discarded.
- */
- assert(rectPtr->right-rectPtr->left<=sizeof(pix)/sizeof(*pix));
- extraSamples=ceil(1000.0/(rectPtr->bottom-rectPtr->top))
- -(rectPtr->right-rectPtr->left);
- if(extraSamples<0)extraSamples=0;
- a=aa=0.0;
- n=0;
- for(y=rectPtr->top; y<rectPtr->bottom; y++){
- pL=(long *)pix;
- for(x=rectPtr->right-rectPtr->left-1; x>=-extraSamples; x--){
- do{
- sample=floor(0.5+*meanPtr+binomialFactor
- *(BinomialSampleQuickly(binomialSamples)-binomialSamples/2));
- }while(sample<low || sample>high);
- a+=sample;
- aa+=(long)sample*(long)sample;
- n++;
- if(x>=0)*pL++=sample;
- }
- SetWindowPixelsQuickly((WindowPtr)world,rectPtr->left,y
- ,pix,rectPtr->right-rectPtr->left);
- }
- actualMean=a/n;
- actualSd=sqrt(aa/n-actualMean*actualMean);
- if(diagnostics)printf("unclippedSd %f, actualSd %f, actualSd/unclippedSd %f, n %ld\n"
- ,*sdPtr,actualSd,actualSd/(*sdPtr),n);
- break;
- default:
- PrintfExit("%s,%d: No such pdf %d\n",__FILE__,__LINE__,(int)pdfKind);
- }
- if(diagnostics){
- ImageStatistics(world,rectPtr,&actualMin,&actualMax,&actualMean,&meanSquare);
- if(actualMax>max || actualMin<min)printf("\nIMAGE OVERFLOW ERROR:\n");
- printf("noise actualMin %ld≥%d, actualMax %ld≤%d, actualMean %.1f≈%.1f, rms %.1f≈%.1f≈%.1f\n"
- ,actualMin,(int)min
- ,actualMax,(int)max
- ,actualMean,*meanPtr
- ,sqrt(meanSquare),actualSd,*sdPtr);
- assert(actualMax<=max && actualMin>=min);
- }
- done:
- *sdPtr=actualSd;
- *meanPtr=actualMean;
- return error;
- }
-
- OSErr NoisePdfAdd(GWorldPtr world,Rect *rectPtr
- ,int pdfKind,double *meanPtr,double *sdPtr,int min,int max)
- {
- GWorldPtr noiseWorld;
- GDHandle device;
- int error=0,pixelSize,id;
- double meanSquare,actualMean;
- long actualMin,actualMax;
- ColorTable **ct;
- long copyMode;
-
- assert(world!=NULL);
- device=GetWindowDevice((WindowPtr)world);
- id=pixelSize=(**(**device).gdPMap).pixelSize;
- if(id>2)id+=32;
- ct=GetCTable(id); // gray ramp
- assert(ct!=NULL);
- error=NewGWorld(&noiseWorld,pixelSize,rectPtr,ct,0,0);
- if(error)error=NewGWorld(&noiseWorld,pixelSize,rectPtr,ct,0,useTempMem);
- if(error)return error;
- error=NoisePdfFill(noiseWorld,rectPtr,pdfKind,meanPtr,sdPtr,min,max);
- if(error)goto done;
- if(min>=0)copyMode=addOverParallelLiterally; // no overflow
- else copyMode=addOver; // "sign" bit may overflow
- error=CopyWindows(noiseWorld,world,rectPtr,rectPtr,copyMode,NULL);
- if(error)PrintfExit("%s line %d: CopyWindows error %d\n",__FILE__,__LINE__,error);
- if(diagnostics){
- ImageStatistics(world,&world->portRect,&actualMin,&actualMax,&actualMean,&meanSquare);
- if(actualMax>max || actualMin<min || diagnostics){
- printf("signal+noise actualMin %ld, actualMax %ld, actualMean %.1f, actualSd %.1f\n"
- ,actualMin,actualMax,actualMean,sqrt(meanSquare-actualMean*actualMean));
- }
- }
- done:
- DisposeGWorld(noiseWorld);
- return error;
- }
-
- void GetGoodNoisePdfBounds(int pdfKind,double mean,double clippedSd
- ,double *unclippedSdPtr,int *minPtr,int *maxPtr)
- {
- double dMax,dMin,clippedSd1;
- int low,high;
-
- assert(unclippedSdPtr!=NULL && minPtr!=NULL && maxPtr!=NULL);
- assert(clippedSd>=0);
- assert(*maxPtr>=*minPtr);
-
- // dMax,dMin,clippedSd1
- switch(pdfKind){
- case kBinaryPdf:
- dMax=clippedSd;
- dMin=-dMax;
- clippedSd1=1;
- break;
- case kUniformPdf:
- dMax=sqrt(3)*clippedSd-0.5;
- dMin=-dMax;
- clippedSd1=1;
- break;
- case kBinomialPdf:
- dMax=2*clippedSd;
- dMin=-dMax;
- clippedSd1=0.85;
- break;
- case kGaussianPdf:
- dMax=2*clippedSd;
- dMin=-dMax;
- clippedSd1=0.737;
- break;
- }
- // high and low are the extremes of the pdf.
- // The high-low difference is more important than the absolute value of either.
- assert(dMax>=dMin);
- low=MAX(floor(0.5+mean+dMin),*minPtr);
- high=MIN(floor(0.5+low+(dMax-dMin)),*maxPtr);
- low=MAX(floor(0.5+high-(dMax-dMin)),*minPtr);
- if(low==high && clippedSd>0){
- if(high<*maxPtr)high++;
- else if(low>*minPtr)low--;
- }
- *minPtr=low;
- *maxPtr=high;
- *unclippedSdPtr=clippedSd/clippedSd1;
- }